The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
Changes 022
MANIFEST 04
META.yml 44
lib/Git/Repository/Command.pm 658
lib/Git/Repository/Log/Iterator.pm 22
lib/Git/Repository/Tutorial.pod 0240
lib/Git/Repository.pm 14334
t/07-version.t 34
t/13-sudo.t 015
t/21-submodule.t 029
t/40-plugin-log.t 119
t/sudo.pl 05
12 files changed (This is a version diff) 159436
@@ -1,5 +1,27 @@
 Revision history for Git-Repository
 
+1.20 Thu Jun  9 14:08:43 CEST 2011
+        [ENHANCEMENTS]
+        - None. It's as good as 1.19, without the stupid test fail.
+        [TESTS]
+        - one test always failed if run outside of a git repository,
+          so I didn't detect it, but all testers did :-(
+          Kazuhiro Shibuya provided a patch!
+
+1.19 Wed Jun  8 23:39:22 CEST 2011
+        [ENHANCEMENTS]
+        - new final_output() method to Git::Repository::Command,
+          that does the git-specific error checking when collecting
+          the final output
+        - Git::Repository::Log::Iterator will now properly die/warn
+          when the log command is incorrect (thanks to Lasse Makholm
+          for the bug report and proposed patch)
+        - Git::Repository::Command now supports an arrayref as the 'git'
+          option value, thus allowing calling wrappers like sudo
+          (thanks to Dominic Humphries for the initial patch)
+        [DOCUMENTATION]
+        - moved the HOWTO part of the doc to Git::Repository::Tutorial
+
 1.18 Sat Apr 16 13:47:26 CEST 2011
         [ENHANCEMENTS]
         - the create() method was fragile (parsing the output of
@@ -6,6 +6,7 @@ lib/Git/Repository/Log.pm
 lib/Git/Repository/Log/Iterator.pm
 lib/Git/Repository/Plugin.pm
 lib/Git/Repository/Plugin/Log.pm
+lib/Git/Repository/Tutorial.pod
 lib/Test/Git.pm
 Makefile.PL
 MANIFEST			This list of files
@@ -19,7 +20,9 @@ t/07-version.t
 t/10-new_fail.t
 t/11-create.t
 t/12-create.t
+t/13-sudo.t
 t/20-simple.t
+t/21-submodule.t
 t/22-backward.t
 t/25-plugins.t
 t/30-log.t
@@ -30,3 +33,4 @@ t/Git/Repository/Plugin/Hello2.pm
 t/MyGit/Hello.pm
 t/pod-coverage.t
 t/pod.t
+t/sudo.pl
@@ -15,16 +15,16 @@ name: Git-Repository
 provides:
   Git::Repository:
     file: lib/Git/Repository.pm
-    version: 1.18
+    version: 1.20
   Git::Repository::Command:
     file: lib/Git/Repository/Command.pm
-    version: 1.12
+    version: 1.13
   Git::Repository::Log:
     file: lib/Git/Repository/Log.pm
     version: 1.01
   Git::Repository::Log::Iterator:
     file: lib/Git/Repository/Log/Iterator.pm
-    version: 1.00
+    version: 1.01
   Git::Repository::Plugin:
     file: lib/Git/Repository/Plugin.pm
     version: 1.01
@@ -44,4 +44,4 @@ requires:
 resources:
   license: http://dev.perl.org/licenses/
   repository: http://github.com/book/Git-Repository
-version: 1.18
+version: 1.20
@@ -13,7 +13,7 @@ use File::Spec;
 use Config;
 use System::Command;
 
-our $VERSION = '1.12';
+our $VERSION = '1.13';
 our @ISA = qw( System::Command );
 
 
@@ -30,7 +30,10 @@ for my $attr (qw( cmdline )) {
 # CAN I HAS GIT?
 my %binary;    # cache calls to _is_git
 sub _is_git {
-    my ($binary) = @_;
+    my ( $binary, @args ) = @_;
+
+    # git option might be an arrayref containing an executable with arguments
+    # Best that can be done is to check if the first part is executable
 
     # compute cache key:
     # - filename (path):     path
@@ -75,7 +78,8 @@ sub _is_git {
         if !( defined $git && -x $git );
 
     # try to run it
-    my ( $pid, $in, $out, $err ) = __PACKAGE__->spawn( $git, '--version' );
+    my ( $pid, $in, $out, $err )
+        = __PACKAGE__->spawn( $git, @args, '--version' );
     my $version = <$out>;
 
     # does it really look like git?
@@ -118,8 +122,12 @@ sub new {
 
     # get and check the git command
     my $git_cmd = ( map { exists $_->{git} ? $_->{git} : () } @o )[-1];
-    $git_cmd = 'git' if !defined $git_cmd;
-    my $git = _is_git($git_cmd);
+
+    # git option might be an arrayref containing an executable with arguments
+    # (e.g. [ qw( /usr/bin/sudo -u nobody git ) ] )
+    ( $git_cmd, my @args )
+        = defined $git_cmd ? ref $git_cmd ? @$git_cmd : ($git_cmd) : ('git');
+    my $git = _is_git($git_cmd, @args);
 
     croak "git binary '$git_cmd' not available or broken"
         if !defined $git;
@@ -128,7 +136,31 @@ sub new {
     delete $ENV{TERM};
 
     # spawn the command and re-bless the object in our class
-    return bless System::Command->new( $git, @cmd, @o ), $class;
+    return bless System::Command->new( $git, @args, @cmd, @o ), $class;
+}
+
+sub final_output {
+    my ($self) = @_;
+
+    # get output / errput
+    my ( $stdout, $stderr ) = @{$self}{qw(stdout stderr)};
+    chomp( my @output = <$stdout> );
+    chomp( my @errput = <$stderr> );
+
+    # done with it
+    $self->close;
+
+    # exit codes: 128 => fatal, 129 => usage
+    my $exit = $self->{exit};
+    if ( $exit == 128 || $exit == 129 ) {
+        croak join( "\n", @errput ) || 'fatal: unknown git error';
+    }
+
+    # something else's wrong
+    if (@errput) { carp join "\n", @errput; }
+
+    # return the output
+    return wantarray ? @output : join "\n", @output;
 }
 
 1;
@@ -200,6 +232,12 @@ I<option> hashes. The recognized keys are:
 
 The actual git binary to run. By default, it is just C<git>.
 
+In case the C<git> to be run is actually a command with parameters
+(e.g. when using B<sudo> or another command executer), the option value
+should be an array reference with the command and parameters, like this:
+
+    { git => [qw( sudo -u nobody git )] }
+
 =item C<cwd>
 
 The I<current working directory> in which the git command will be run.
@@ -241,6 +279,20 @@ number of attributes defined (see below).
 Close all pipes to the child process, and collects exit status, etc.
 and defines a number of attributes (see below).
 
+=head2 final_output()
+
+Collect all the output, and terminate the command.
+
+Returns the output as a string in scalar context,
+or as a list of lines in list context. Also accepts a hashref of options.
+
+Lines are automatically C<chomp>ed.
+
+If the Git command printed anything on stderr, it will be printed as
+warnings. If the git sub-process exited with status C<128> (fatal error),
+or C<129> (usage message), it will C<die()>.
+
+
 =head2 Accessors
 
 The attributes of a C<Git::Repository::Command> object are also accessible
@@ -9,7 +9,7 @@ use Git::Repository;
 use Git::Repository::Command;
 use Git::Repository::Log;
 
-our $VERSION = '1.00';
+our $VERSION = '1.01';
 
 sub new {
     my ( $class, @cmd ) = @_;
@@ -46,7 +46,7 @@ sub next {
     }
 
     # EOF
-    return if !@records;
+    return $self->{cmd}->final_output() if !@records;
 
     # the first two records are always the same, with --pretty=raw
     my ( $header, $message, $extra ) = ( @records, '' );
@@ -0,0 +1,240 @@
+=head1 NAME
+
+Git::Repository::Tutorial - Control git from Perl using Git::Repository
+
+=head1 SYNOPSIS
+
+    use Git::Repository;
+
+    # do cool stuff with Git, using the following advice
+
+=head1 HOW-TO
+
+A C<Git::Repository> object represents an actual Git repository,
+against which you can I<run> commands.
+
+=head2 Obtain a Git::Repository object from an existing repository
+
+If your script is expected to run against a repository in the current
+directory (like most Git commands), let C<Git::Repository> handle
+the magic:
+
+    $r = Git::Repository->new();
+
+If the repository has a working copy (work tree):
+
+    $r = Git::Repository->new( work_tree => $dir );
+
+If the repository is a bare repository, or you prefer to provide
+the F<.git> directory location:
+
+    $r = Git::Repository->new( git_dir => $gitdir );
+
+If the work tree and the git directory are in unrelated locations,
+you can also provide both:
+
+    $r = Git::Repository->new( work_tree => $dir, git_dir => $gitdir );
+
+The constructor also accepts an option hash. The various options
+are detailed in the manual page for C<Git::Repository::Command>.
+
+=head2 Run any git command
+
+Git commands can be run against an existing C<Git::Repository> object,
+or against the class itself (in which case, git will try to deduce its
+context from the current directory and the environment).
+
+The pattern for running commands is always the same:
+
+    $r->run( $command => @arguments, \%options );
+
+The C<$command> and C<@arguments> are identical to those you'd pass to
+the C<git> command-line tool. The options hash contains options, as
+described in the manual page for C<Git::Repository::Command>.
+
+=head2 Create a new repository
+
+Sometime, you'll need to create the Git repository from scratch:
+
+    # git version 1.6.5 and above
+    Git::Repository->run( init => $dir );
+    $r = Git::Repository->new( work_tree => $dir );
+
+    # any older git requires the command to be run in the work tree,
+    # so we use the cwd option
+    Git::Repository->run( init => { cwd => $dir } );
+    $r = Git::Repository->new( work_tree => $dir );
+
+Note that the old C<create()> method is obsolete, warns and will be removed
+in a future version.
+
+=head2 Clone a repository
+
+Cloning works the same way:
+
+    Git::Repository->run( clone => $url => $dir );
+    $r = Git::Repository->new( $dir );
+
+=head2 Run a simple command
+
+When you don't really care about the output of the command, just call
+it:
+
+    $r->run( add => '.' );
+    $r->run( commit => '-m', 'my commit message' );
+
+In case of an error or warning, C<Git::Repository> will C<croak()> or
+C<carp()> appropriately.
+
+=head2 Process normal and error output
+
+The C<run()> command doesn't capture stderr: it only warns (or dies)
+if something was printed on it. To be able to actually capture error
+output, C<command()> must be used.
+
+    my $cmd = $r->command( @cmd );
+    my @errput = $cmd->stderr->getlines();
+    $cmd->close;
+
+C<run()> also captures all output at once, which can lead to unnecessary
+memory consumption when capturing the output of some really verbose
+commands.
+
+    my $cmd = $r->command( log => '--pretty=oneline', '--all' );
+    my $log = $cmd->stdout;
+    while (<$log>) {
+        ...;
+    }
+    $cmd->close;
+
+Of course, as soon as one starts reading and writing to an external
+process' communication handles, a risk of blocking exists.
+I<Caveat emptor>.
+
+=head2 Provide input on standard input
+
+Use the C<input> option:
+
+    my $commit = $r->run( 'commit-tree', $tree, '-p', $parent,
+        { input => $message } );
+
+=head2 Change the environment of a command
+
+Use the C<env> option:
+
+    $r->run(
+        'commit', '-m', 'log message',
+        {   env => {
+                GIT_COMMITTER_NAME  => 'Git::Repository',
+                GIT_COMMITTER_EMAIL => 'book@cpan.org',
+            },
+        },
+    );
+
+See L<Git::Repository::Command> for other available options.
+
+=head2 Process the output of B<git log>
+
+When creating a tool that needs to process the output of B<git log>,
+you should always define precisely the expected format using the
+I<--pretty> option, and choose a format that is easy to parse.
+
+Assuming B<git log> will output the default format will eventually
+lead to problems, for example when the user's git configuration defines
+C<format.pretty> to be something else than the default of C<medium>.
+
+=head2 Process the output of B<git shortlog>
+
+B<git shortlog> behaves differently when it detects it's not attached
+to a terminal. In that case, it just tries to read some B<git log>
+output from its standard input.
+
+So this oneliner will hang, because B<git shortlog> is waiting for some
+data from the program connected to its standard input (the oneliner):
+
+    perl -MGit::Repository -le 'print scalar Git::Repository->run( shortlog => -5 )'
+
+Whereas this one will "work" (as in "immediately return with no output"):
+
+    perl -MGit::Repository -le 'print scalar Git::Repository->run( shortlog => -5, { input => "" } )'
+
+So, you need to give B<git shortlog> I<some> input (from B<git log>):
+
+    perl -MGit::Repository -le 'print scalar Git::Repository->run( shortlog => { input => scalar Git::Repository->run( log => -5 ) } )'
+
+If the log output is large, you'll probably be better off with something
+like the following:
+
+    use Git::Repository;
+
+    # start both git commands
+    my $log = Git::Repository->command('log')->stdout;
+    my $cmd = Git::Repository->command( shortlog => -ens );
+
+    # feed one with the output of the other
+    my $in = $cmd->stdin;
+    print {$in} $_ while <$log>;
+    close $in;
+
+    # and do something with the output
+    print $cmd->stdout->getlines;
+
+=head2 Wrap git in a sudo call
+
+If for a given repository you want to wrap all calls to git in a C<sudo>
+call, you can use the C<git> option with an array ref:
+
+    my $r = Git::Repository->new( { git => [qw( sudo -u nobody git )] } );
+
+In this case, every call to git from C<$r> will actually call
+C<sudo -u nobody git>.
+
+=head2 Use submodules
+
+Because C<Git::Repository> automatically sets the C<GIT_DIR> and
+C<GIT_WORK_TREE> environment variables, some C<submodule> sub-commands
+may fail. For example:
+
+    $r->run( submodule => add => $repository => 'sub' );
+
+will give the following error:
+
+    error: pathspec 'sub' did not match any file(s) known to git.
+
+To avoid this error, you should enforce the removal of the C<GIT_WORK_TREE>
+variable from the environment in which the command is run:
+
+    $r->run(
+        submodule => add => $repository => 'sub',
+        { env => { GIT_WORK_TREE => undef } }
+    );
+
+Note that C<System::Command> version 1.04 is required to be able to remove
+variables from the environment.
+
+=head2 Sort git versions
+
+Basically, you need to recreate the C<cmp> operator for Git versions,
+using the I<private> C<_version_gt()> method (which accepts two parameters):
+
+    @sorted_versions = sort {
+        Git::Repository::_version_gt( $a, $b )
+            || -Git::Repository::_version_gt( $b, $a )
+    } @versions;
+
+
+=head1 AUTHOR
+
+Philippe Bruhat (BooK), C<< <book at cpan.org> >>
+
+=head1 COPYRIGHT
+
+Copyright 2010-2011 Philippe Bruhat (BooK), all rights reserved.
+
+=head1 LICENSE
+
+This documenation is free software; you can redistribute it and/or modify it
+under the same terms as Perl itself.
+
+=cut
+
@@ -11,7 +11,7 @@ use Scalar::Util qw( looks_like_number );
 
 use Git::Repository::Command;
 
-our $VERSION = '1.18';
+our $VERSION = '1.20';
 
 # a few simple accessors
 for my $attr (qw( git_dir work_tree options )) {
@@ -109,7 +109,7 @@ sub new {
         $self->{git_dir} = $git_dir if defined $git_dir;
 
         # in a non-bare repository, the work tree is just above the gitdir
-        if ( $self->run(qw( config core.bare )) ne 'true' ) {
+        if ( $self->run(qw( config --bool core.bare )) ne 'true' ) {
             $self->{work_tree}
                 = _abs_path( File::Spec->updir, $self->{git_dir} );
         }
@@ -191,25 +191,8 @@ sub run {
     my $command
         = Git::Repository::Command->new( ref $self ? $self : (), @cmd );
 
-    # get output / errput
-    my ( $stdout, $stderr ) = @{$command}{qw(stdout stderr)};
-    chomp( my @output = <$stdout> );
-    chomp( my @errput = <$stderr> );
-
-    # done with it
-    $command->close;
-
-    # exit codes: 128 => fatal, 129 => usage
-    my $exit = $command->{exit};
-    if ( $exit == 128 || $exit == 129 ) {
-        croak join( "\n", @errput ) || 'fatal: unknown git error';
-    }
-
-    # something else's wrong
-    if (@errput) { carp join "\n", @errput; }
-
-    # return the output
-    return wantarray ? @output : join "\n", @output;
+    # return the output or die
+    return $command->final_output;
 }
 
 #
@@ -350,7 +333,7 @@ command, whether I<porcelain> or I<plumbing>, including bidirectional
 commands such as C<git commit-tree>.
 
 A C<Git::Repository> object simply provides context to the git commands
-being run. Is it possible to call the  C<command()>and C<run()> methods
+being run. It is possible to call the  C<command()> and C<run()> methods
 against the class itself, and the context (typically I<current working
 directory>) will be obtained from the options and environment.
 
@@ -364,6 +347,11 @@ object, they will be overriden by the object's C<git_dir> and
 C<work_tree> attributes, respectively. It is however still possible to
 override them if necessary, using the C<env> option.
 
+C<Git::Repository> requires at least Git 1.5.0, and is expected to support
+any later version.
+
+See C<Git::Repository::Tutorial> for more code examples.
+
 =head1 CONSTRUCTORS
 
 There are two ways to create C<Git::Repository> objects:
@@ -470,7 +458,7 @@ Lines are automatically C<chomp>ed.
 
 If the git command printed anything on stderr, it will be printed as
 warnings. If the git sub-process exited with status C<128> (fatal error),
-C<run()> will C<die()>.
+or C<129> (usage message), C<run()> will C<die()>.
 
 =head2 git_dir()
 
@@ -555,119 +543,6 @@ supported a given feature. The precise commit in git.git at which a given
 feature was added doesn't mean as much as the release branch in which that
 commit was merged.
 
-=head1 HOW-TO
-
-=head2 Create a new repository
-
-    # git version 1.6.5 and above
-    my $r = Git::Repository->create( init => $dir );
-
-    # any older git will need two steps
-    chdir $dir;
-    my $r = Git::Repository->create( 'init' );
-
-=head2 Clone a repository
-
-    my $r = Git::Repository->create( clone => $url => $dir );
-
-=head2 Run a simple command
-
-    $r->run( add => '.' );
-    $r->run( commit => '-m', 'my commit message' );
-
-=head2 Process normal and error output
-
-The C<run()> command doesn't capture stderr: it only warns (or dies)
-if something was printed on it. To be able to actually capture error
-output, C<command()> must be used.
-
-    my $cmd = $r->command( @cmd );
-    my @errput = $cmd->stderr->getlines();
-    $cmd->close;
-
-C<run()> also captures all output at once, which can lead to unnecessary
-memory consumption when capturing the output of some really verbose
-commands.
-
-    my $cmd = $r->command( log => '--pretty=oneline', '--all' );
-    my $log = $cmd->stdout;
-    while (<$log>) {
-        ...;
-    }
-    $cmd->close;
-
-Of course, as soon as one starts reading and writing to an external
-process' communication handles, a risk of blocking exists.
-I<Caveat emptor>.
-
-=head2 Provide input on standard input
-
-Use the C<input> option:
-
-    my $commit = $r->run( 'commit-tree', $tree, '-p', $parent,
-        { input => $message } );
-
-=head2 Change the environment of a command
-
-Use the C<env> option:
-
-    $r->run(
-        'commit', '-m', 'log message',
-        {   env => {
-                GIT_COMMITTER_NAME  => 'Git::Repository',
-                GIT_COMMITTER_EMAIL => 'book@cpan.org',
-            },
-        },
-    );
-
-See L<Git::Repository::Command> for other available options.
-
-=head2 Process the output of B<git log>
-
-When creating a tool that needs to process the output of B<git log>,
-you should always define precisely the expected format using the
-I<--pretty> option, and choose a format that is easy to parse.
-
-Assuming B<git log> will output the default format will eventually
-lead to problems, for example when the user's git configuration defines
-C<format.pretty> to be something else than the default of C<medium>.
-
-=head2 Process the output of B<git shortlog>
-
-B<git shortlog> behaves differently when it detects it's not attached
-to a terminal. In that case, it just tries to read some B<git log>
-output from its standard input.
-
-So this oneliner will hang, because B<git shortlog> is waiting for some
-data from the program connected to its standard input (the oneliner):
-
-    perl -MGit::Repository -le 'print scalar Git::Repository->run( shortlog => -5 )'
-
-Whereas this one will "work" (as in "immediately return with no output"):
-
-    perl -MGit::Repository -le 'print scalar Git::Repository->run( shortlog => -5, { input => "" } )'
-
-So, you need to give B<git shortlog> I<some> input (from B<git log>):
-
-    perl -MGit::Repository -le 'print scalar Git::Repository->run( shortlog => { input => scalar Git::Repository->run( log => -5 ) } )'
-
-If the log output is large, you'll probably be better off with something
-like the following:
-
-    use Git::Repository;
-
-    # start both git commands
-    my $log = Git::Repository->command('log')->stdout;
-    my $cmd = Git::Repository->command( shortlog => -ens );
-
-    # feed one with the output of the other
-    my $in = $cmd->stdin;
-    print {$in} $_ while <$log>;
-    close $in;
-
-    # and do something with the output
-    print $cmd->stdout->getlines;
-
 =head1 PLUGIN SUPPORT
 
 C<Git::Repository> intentionally has only few methods.
@@ -715,6 +590,20 @@ revision history of a project. A lot of those commands can output
 huge amounts of data, which I need to be able to process in chunks.
 Some of these commands also expect to receive input.
 
+What follows is a short list of "missing features" that I was looking
+for when I looked at the existing Git wrappers on CPAN. They are the
+"rational" reason for writing my own (the real reason being of course
+"I thought it would be fun, and I enjoyed doing it").
+
+Even though it works well for me and others, C<Git::Repository> has its
+own shortcomings: it I<is> a I<low-level interface to Git commands>,
+anything complex requires you to deal with input/output handles,
+it provides no high-level interface to generate actual Git commands
+or process the output of commands (but have a look at the plugins),
+it doesn't fully work under Win32 yet, etc. One the following modules
+may therefore be better suited for your needs, depending on what you're
+trying to achieve.
+
 =head2 Git.pm
 
 Git.pm is not on CPAN. It is usually packaged with Git, and installed with
@@ -730,9 +619,11 @@ L<http://kerneltrap.org/mailarchive/git/2008/10/24/3789584>
 =head2 Git::Class
 
 Depends on Moose, which seems an unnecessary dependency for a simple
-wrapper around Git.
+wrapper around Git. The startup penalty could become significant for
+command-line tools.
 
-Although it supports C<git init> and C<git clone>, it is mostly aimed at
+Although it supports C<git init> and C<git clone>
+(and has methods to call any Git command), it is mostly aimed at
 porcelain commands, and provides no way to control bidirectional commands
 (such as C<git commit-tree>).
 
@@ -748,11 +639,11 @@ Philippe Bruhat (BooK), C<< <book at cpan.org> >>
 
 =head1 BUGS
 
-On Win32, in some cases of failure of the underlying Git command,
-the C<run()> method is not able to catch the error output on STDERR.
-In those cases, C<Git::Repository> will croak C<fatal: unknown git error>
-instead of the original Git error message. Bugfixes and explanations
-are very welcome.
+Since version 1.17, C<Git::Repository> delegates the actual command
+execution to C<System::Command>. Win32 support for that module is
+currently very bad (the test suite hangs in a few places).
+If you'd like better Win32 support for C<Git::Repository>, help me improve
+C<System::Command>!
 
 Please report any bugs or feature requests to C<bug-git-repository at rt.cpan.org>, or through
 the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Git-Repository>.  I will be notified, and then you'll
@@ -49,12 +49,13 @@ for my $t (@true) {
 # helper routine to build a fake fit binary
 sub fake_git {
     my ($version) = @_;
-    my ( $fh, $filename ) = tempfile( MSWin32 ? ( SUFFIX => '.bat' ) : () );
+    my ( $fh, $filename ) =
+      tempfile( UNLINK => 1, MSWin32 ? ( SUFFIX => '.bat' ) : () );
     print {$fh} MSWin32 ? << "WIN32" : << "UNIX";
 \@echo git version $version
 WIN32
-#!/bin/sh
-$^X -le 'print "git version $version"'
+#!$^X
+print "git version $version\\n"
 UNIX
     close $fh;
     chmod 0755, $filename;
@@ -0,0 +1,15 @@
+use strict;
+use warnings;
+use Test::More;
+use Test::Git;
+use File::Spec;
+
+has_git();
+
+plan tests => 1;
+
+# test using a wrapper
+my $sudo = File::Spec->catfile( t => 'sudo.pl' );
+my $out = Git::Repository->run( qw( a b ), { git => [ $^X, $sudo, 'c' ] } );
+is( $out, 'c a b', 'wrapper called correctly' );
+
@@ -0,0 +1,29 @@
+use strict;
+use warnings;
+use Test::More;
+use Test::Git;
+use Git::Repository;
+
+plan skip_all =>
+    "Removing environment variables requires System::Command 1.04, this is only $System::Command::VERSION"
+    if $System::Command::VERSION < 1.04;
+
+plan tests => 1;
+
+# create a small repository
+my $s      = test_repository;
+my $tree   = $s->run( mktree => { input => '' } );
+my $commit = $s->run( 'commit-tree' => $tree, { input => 'empty tree' } );
+$s->run( 'update-ref', 'refs/heads/master' => $commit );
+
+# now test adding a submodule
+my $r = test_repository;
+$r->run(
+    submodule => add => $s->work_tree => 'sub',
+    { env => { GIT_WORK_TREE => undef } }
+);
+
+# do the test
+my $status = $r->run('submodule', 'status', 'sub' );
+is( $status, "-$commit sub", 'git submodule status' );
+
@@ -98,6 +98,24 @@ check_commit( 2 => $log[0] );
 
 chdir $home;
 
+# try a command that fails (fatal)
+BEGIN { $tests += 2 }
+ok( !eval { @log = $r->log('zlonk') }, q{log('zlonk') failed} );
+like(
+    $@,
+    qr/^fatal: ambiguous argument 'zlonk': unknown revision or path not in/,
+    'unknown revision or path'
+);
+
+# try a command that returns a git error (usage)
+BEGIN { $tests += 2 }
+ok( !eval { @log = $r->log('--bam') }, q{log('--bam') failed} );
+like(
+    $@,
+    qr/^fatal: unrecognized argument: --bam at/,
+    'unknown revision or path'
+);
+
 # various options combinations
 my @options;
 
@@ -125,10 +143,10 @@ DIFF
 
 for my $o (@options) {
     my ( $args, $extra, $minver ) = @$o;
-    @log = $r->log(@$args);
 SKIP: {
         skip "git log @$args needs $minver, we only have $version", 13
             if $minver && Git::Repository->version_lt($minver);
+        @log = $r->log(@$args);
         is( scalar @log, 2, "2 commits for @$args" );
         isa_ok( $_, 'Git::Repository::Log' ) for @log;
         check_commit( 2 => $log[0], extra => $extra->[0] );
@@ -0,0 +1,5 @@
+#!/usr/bin/env perl
+
+# a tiny fake git wrapper
+print "@ARGV" =~ /version/ ? "git version 9.8.7\n" : "@ARGV\n";
+